﻿using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;
using Severity = Microsoft.Scripting.Severity;
using TokenTriggers = Microsoft.VisualStudio.Package.TokenTriggers;
using VsTokenInfo = Microsoft.VisualStudio.Package.TokenInfo;

namespace YaTools.Yaml.LanguagePackage
{
    internal class YamlColorizer : Colorizer
    {
        private readonly SeekableYamlScanner _scanner;
        private readonly List<VsTokenInfo> _tokenInfoList = new List<VsTokenInfo>();
        private int _lineNumberTemp;

        public YamlColorizer(LanguageService svc, IVsTextLines buffer, ScriptSource scriptSource)
            : base(svc, buffer, new DummyScanner())
        {
            _scanner = new SeekableYamlScanner();
            _scanner.ErrorSink = MyNullErrorSink.Instance;
            _scanner.SetSource(scriptSource);
        }

        private void SetupScanner(int lineNo, string text, int state)
        {
            var location = new SourceLocation(0, lineNo + 1, 1);
            _scanner.SeekState(location, text, state);
        }

        private void SetupScanner(int lineNo, int length, IntPtr pText, int state)
        {
            string text = Marshal.PtrToStringUni(pText, length);
            SetupScanner(lineNo, text, state);
        }

        public override void CloseColorizer()
        {
            _scanner.Dispose();
            base.CloseColorizer();
        }

        /// <summary>
        /// Obtains color and font attribute information for 
        /// each character in the specified line of text. 
        /// </summary>
        /// <param name="line"></param>
        /// <param name="length"></param>
        /// <param name="ptr"></param>
        /// <param name="state"></param>
        /// <param name="attrs"></param>
        /// <returns></returns>
        public override int ColorizeLine(int line, int length, IntPtr ptr, int state, uint[] attrs)
        {
            if (attrs != null)
                Array.Clear(attrs, 0, attrs.Length);
            try
            {
                SetupScanner(line, length, ptr, state);
                Token token;
                while ((token = _scanner.NextToken()) != Token.EndOfStream)
                {
                    SourceSpan span = _scanner.TokenSpan;
                    var color = (uint) ChooseColor(token);
                    if (color == 0) continue;
                    for (int i = span.Start.Column - 1; i < span.End.Column - 1; i++)
                        if (attrs != null && i < attrs.Length)
                            attrs[i] = color;
                }
                return _scanner.TellState();
            }
            catch (Exception)
            {
                return 0;
            }
        }

        private YamlColor ChooseColor(Token token)
        {
            switch (token)
            {
                case Token.Comment:
                    return YamlColor.Comment;

                case Token.Directive:
                case Token.DirectivesEnd:
                case Token.DocumentEnd:
                    return YamlColor.Document;

                case Token.ImplicitKey:
                case Token.PlainEnd:
                    break;

                case Token.Escape:
                case Token.EscapeWhiteSpace:
                    return YamlColor.Escaped;


                case Token.ValueIndicator:
                case Token.Literal:
                case Token.Folded:
                case Token.FlowKeyIndicator:
                case Token.BlockKeyIndicator:
                case Token.OpenBrace:
                case Token.CloseBrace:
                case Token.OpenBracket:
                case Token.CloseBracket:
                case Token.Comma:
                case Token.Outdent:
                    return YamlColor.Indicator;

                case Token.SingleQuote:
                case Token.DoubleQuote:
                    return YamlColor.QuotationMark;

                case Token.IndentSpaces:
                    return YamlColor.Indent;

                case Token.BlockSeqBegin: // Treat the '-' indicator like indent
                case Token.BlockSeqNext:
                    return YamlColor.Indent;

                case Token.InconsistentIndent:
                    return YamlColor.Inconsistant;

                case Token.Unexpected:
                case Token.LeadingWhiteSpace:
                    return YamlColor.Default;

                case Token.TextContent:
                    if (_scanner.InQuotes)
                        return YamlColor.Quoted;
                    if (_scanner.InKey)
                        return YamlColor.ImpliedKey;
                    return YamlColor.Plain;

                case Token.Anchor:
                    return YamlColor.Anchor;

                case Token.Alias:
                    return YamlColor.Alias;

                case Token.Tag:
                    return YamlColor.Tag;

                default:
                    return YamlColor.Default;
            }
            return YamlColor.Ignore;
        }


        public override int GetStateAtEndOfLine(int line, int length, IntPtr ptr, int state)
        {
            try
            {
                SetupScanner(line, length, ptr, state);
                while (_scanner.NextToken() != Token.EndOfStream)
                {
                }
                return _scanner.TellState();
            }
            catch (Exception)
            {
                return 0;
            }
        }

        /// <summary>
        /// Returns the parsing state at the end of the line 
        /// without returning any colorization information
        /// </summary>
        public override int GetColorInfo(string line, int length, int state)
        {
            SetupScanner(_lineNumberTemp, line, state);
            Token token;

            while ((token = _scanner.NextToken()) != Token.EndOfStream)
            {
                SourceSpan span = _scanner.TokenSpan;
                var tokenInfo = new VsTokenInfo
                                    {
                                        StartIndex = span.Start.Column - 1,
                                        EndIndex = span.End.Column - 1,
                                        Color = (TokenColor) (int) ChooseColor(token)
                                    };

                switch (token)
                {
                    case Token.TextContent:
                        tokenInfo.Type = TokenType.Text;
                        break;

                    case Token.BlockSeqBegin:
                    case Token.BlockKeyIndicator:
                    case Token.FlowKeyIndicator:
                    case Token.ValueIndicator:
                    case Token.Comma:
                        tokenInfo.Type = TokenType.Delimiter;
                        break;

                    case Token.OpenBrace:
                    case Token.CloseBrace:
                    case Token.OpenBracket:
                    case Token.CloseBracket:
                        tokenInfo.Type = TokenType.Delimiter;
                        tokenInfo.Trigger = TokenTriggers.MatchBraces;
                        break;

                    case Token.IndentSpaces:
                    case Token.LeadingWhiteSpace:
                        tokenInfo.Type = TokenType.WhiteSpace;
                        break;
                }
                _tokenInfoList.Add(tokenInfo);
            }
            return _scanner.TellState();
        }

        public override VsTokenInfo[] GetLineInfo(
            IVsTextLines buffer, int line, IVsTextColorState colorState)
        {
            _lineNumberTemp = line;
            _tokenInfoList.Clear();
            base.GetLineInfo(buffer, line, colorState);
            return _tokenInfoList.ToArray();
        }

        #region Nested type: DummyScanner

        private class DummyScanner : IScanner
        {
            #region IScanner Members

            public bool ScanTokenAndProvideInfoAboutIt(
                VsTokenInfo tokenInfo, ref int state)
            {
                throw new NotImplementedException();
            }

            public void SetSource(string source, int offset)
            {
                throw new NotImplementedException();
            }

            #endregion
        }

        #endregion

        #region Nested type: MyNullErrorSink

        private class MyNullErrorSink : ErrorSink
        {
            internal static readonly MyNullErrorSink Instance = new MyNullErrorSink();

            public override void Add(SourceUnit source, string message, SourceSpan span,
                                     int errorCode, Severity severity)
            {
            }

            public override void Add(string message, string path, string code, string line,
                                     SourceSpan span, int errorCode, Severity severity)
            {
            }
        }

        #endregion
    }
}